package abel.wlmr.config;

import java.util.HashMap;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import abel.wlmr.model.WlmrPacket;

public class PacketSender {

    private static final Logger log = LoggerFactory.getLogger(PacketSender.class);

    public static final int MAX_TIMES = 3;

    private ReentrantReadWriteLock rw = new ReentrantReadWriteLock();

    private Queue<PacketTimes> ptQueue = new ArrayBlockingQueue<>(128);

    private Map<Byte, PacketTimes> timesMap = new HashMap<>();

    /**
     * Gets next packet to be sent. Remove packet if times exceeds max_times.
     * 
     * @return
     */
    public PacketTimes nextPacket() {
        try {
            rw.writeLock().lock();

            PacketTimes pt = ptQueue.peek();
            if (pt == null) {
                return null;
            }

            ++pt.times;

            // check if there's timeout
            if (pt.times >= MAX_TIMES) {
                ptQueue.poll();
            }

            // log
            log.info("after Get sender cache: {} packets; waiting for reply: {}.", ptQueue.size(), timesMap.size());

            return pt;
        } finally {
            rw.writeLock().unlock();
        }
    }

    public void sendOut(PacketTimes pt) {
        try {
            rw.writeLock().lock();

            timesMap.put(pt.packet.getFseq().getSeq(), pt);
        } finally {
            rw.writeLock().unlock();
        }
    }

    /**
     * Return the request and remove from queue.
     * 
     * @param seq
     * @return
     */
    public PacketTimes gotReply(byte seq) {
        try {
            rw.writeLock().lock();

            PacketTimes pt = timesMap.remove(seq);

            if (pt != null) {
                if (!ptQueue.remove(pt)) {
                    pt = ptQueue.poll();
                }
            } else {
                pt = ptQueue.poll();
            }

            // log
            log.info("after Reply sender cache: {} packets; waiting for reply: {}.", ptQueue.size(), timesMap.size());

            return pt == null ? null : pt;
        } finally {
            rw.writeLock().unlock();
        }
    }

    /**
     * Queues the packet.
     * 
     * @param packet
     */
    public void queuePacket(WlmrPacket packet) {
        PacketTimes pt = new PacketTimes(packet);

        try {
            rw.writeLock().lock();

            ptQueue.add(pt);

            // clear cache if overflow
            if (ptQueue.size() >= 100) {
                for (int i = 0; i < 10; i++) {
                    ptQueue.poll();
                }
            }

            // log
            log.info("after Queue sender cache: {} packets; waiting for reply: {}.", ptQueue.size(), timesMap.size());
        } finally {
            rw.writeLock().unlock();
        }
    }

    public static class PacketTimes {
        private final WlmrPacket packet;

        private int times;

        public PacketTimes(WlmrPacket packet) {
            if (packet == null) {
                throw new IllegalArgumentException("packet can't be null in PacketTimes instance");
            }

            this.packet = packet;
        }

        /**
         * @return the packet
         */
        public WlmrPacket getPacket() {
            return packet;
        }

        /**
         * @return the times
         */
        public int getTimes() {
            return times;
        }
    }
}
